﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using OpenQuarters.WebQuarters.Core;
using System.Web;
using System.Web.UI;
using OpenQuarters.WebQuarters.Utils;
using System.Web.Mvc;
using OpenQuarters.EntityBase;
using System.Data.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Web.Mvc.Html;
using System.IO;

namespace OpenQuarters.WebQuarters.Core
{
    public class ItemListerControl<T> : ViewUserControl<T> where T : class, IItemListable
    {
        private ItemListerConfiguration configuration = new ItemListerConfiguration();

        public ItemListerConfiguration Configuration
        {
            get { return configuration; }
            set { configuration = value; }
        }

        private List<T> items = new List<T>();

        public List<T> Items
        {
            get { return items; }
            set { items = value; }
        }

        private int itemTotal;

        public int ItemTotal
        {
            get { return itemTotal; }
            set { itemTotal = value; }
        }

        private T targetItem = null;

        public T TargetItem
        {
            get { return targetItem; }
            set { targetItem = value; }
        }

        public ItemListerControl()
            : base()
        {
        }

        public ItemListerControl(ItemListerConfiguration config)
            : base()
        {
            this.Configuration = config;
        }

        public List<IItemListable> GetItemsAsIItemListable()
        {
            return BuildItemList().Cast<IItemListable>().ToList();
        }

        public List<T> BuildItemList()
        {
            ParameterExpression fExpParameter = Expression.Parameter(typeof(T), "i");
            Expression fExpFilter = null;

            // Filter items
            foreach (var f in Configuration.Filters)
            {
                object objValue = null;
                try { objValue = f.GetValueAsObject(typeof(T)); }
                catch { continue; }

                try
                {
                    string fieldName = f.Field;
                    var attrs = typeof(T).GetProperty(f.Field).GetCustomAttributes(typeof(PropertyConfigurationAttribute), true).Cast<PropertyConfigurationAttribute>().ToList();
                    if (attrs.Count > 0 && attrs[0].SourceField.Length > 0)
                    {
                        fieldName = attrs[0].SourceField;
                    }
                    Expression fExpLeft = Expression.Property(fExpParameter, fieldName);
                    Expression fExpRight = null;
                    Expression fExp = null;
                    switch (f.OperatorName)
                    {
                        case FilterOperator.Contains:
                        case FilterOperator.Does_not_contain:
                            MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                            fExp = Expression.Call(
                                fExpLeft,
                                containsMethod,
                                new Expression[] { Expression.Constant(objValue, typeof(string)) });

                            if (f.OperatorName == FilterOperator.Does_not_contain)
                            {
                                fExp = Expression.Not(fExp);
                            }
                            break;
                        case FilterOperator.In:
                            string[] values = objValue.ToString().Split(',');
                            List<string> queryable = values.ToList<string>();

                            fExpLeft = Expression.Constant(queryable, typeof(List<string>));

                            MethodInfo containsMethod2 = typeof(List<string>).GetMethod("Contains");

                            fExp = Expression.Call(
                                fExpLeft,
                                containsMethod2,
                                new Expression[] { Expression.Property(fExpParameter, fieldName) });

                            break;
                        default:
                            fExpRight = Expression.Constant(objValue);
                            break;
                    }

                    switch (f.OperatorName)
                    {
                        case FilterOperator.Equals:
                            fExp = Expression.Equal(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Does_not_equal:
                            fExp = Expression.NotEqual(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Less_than:
                            fExp = Expression.LessThan(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Less_than_or_equal_to:
                            fExp = Expression.LessThanOrEqual(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.More_than:
                            fExp = Expression.GreaterThan(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.More_than_or_equal_to:
                            fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight);
                            break;
                    }

                    if (fExp != null)
                    {
                        if (fExpFilter == null)
                        {
                            fExpFilter = fExp;
                        }
                        else
                        {
                            fExpFilter = Expression.AndAlso(fExpFilter, fExp);
                        }
                    }
                }
                catch { continue; }
            }

            var tbl = Activator.CreateInstance<T>().GetQueryableList();
            items = new List<T>();

            MethodCallExpression expFull = null;

            if (fExpFilter != null)
            {
                expFull = Expression.Call(
                    typeof(Queryable),
                    "Where",
                    new Type[] { typeof(T) },
                    tbl.Expression,
                    Expression.Lambda<Func<T, bool>>(fExpFilter, new ParameterExpression[] { fExpParameter }));
            }

            // Ordering
            bool bOrderedFirst = false;
            foreach (var o in Configuration.Ordering)
            {
                try
                {
                    string fieldName = o.Field;
                    var attrs = typeof(T).GetProperty(o.Field).GetCustomAttributes(typeof(PropertyConfigurationAttribute), true).Cast<PropertyConfigurationAttribute>().ToList();
                    if (attrs.Count > 0 && attrs[0].SourceField.Length > 0)
                    {
                        fieldName = attrs[0].SourceField;
                    }

                    Type resultType = null;
                    LambdaExpression orderByLambda = Utils.LinqUtils.GenerateSelector<T>(fieldName, out resultType);

                    expFull = Expression.Call(
                        typeof(Queryable),
                        (bOrderedFirst ? "ThenBy" : "OrderBy") + (o.Operator == "desc" ? "Descending" : ""),
                        new Type[] { typeof(T), resultType },
                        expFull == null ? tbl.Expression : expFull,
                        Expression.Quote(orderByLambda));

                    bOrderedFirst = true;
                }
                catch { continue; }
            }

            if (!string.IsNullOrEmpty(this.Configuration.Method))
            {
                //itemTotal = ((List<T>)GeneralUtils.CallMethod(this.Configuration.Method)).Count();

                //TODO: Find a way to call the filter expression on the custom method 

                tbl = ((IQueryable<T>)GeneralUtils.CallMethod(this.Configuration.Method)).AsQueryable<T>();

                //items = ((List<T>)GeneralUtils.CallMethod(this.Configuration.Method)).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).ToList();
            }
            else
            {
                //itemTotal = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Count();

                //items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).ToList();


            }

            itemTotal = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Count();

            if (Configuration.Current_Page == 0 && Configuration.Items_Per_Page == 0)
                items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).ToList();
            else
                items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).ToList();

            return this.Items;
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            if (items.Count == 0)
            {
                BuildItemList();
            }

            if (Request.Form["createItem"] == "true")
            {
                var createdItem = (T)Activator.CreateInstance<T>().CreateItem(this);
                items.Insert(0, createdItem);
                Configuration.DraftItems.Clear();
                Configuration.DraftItems.Add(new ItemDefinition(createdItem));
            }
            else
            {
                var removeDrafts = new List<ItemDefinition>();
                foreach (var i in Configuration.DraftItems)
                {
                    if (i.type == typeof(T).FullName && items.FirstOrDefault(x => i.Item.GetIDAsString() == x.GetIDAsString() && i.type == x.GetType().FullName) == null)
                    {
                        items.Add((T)i.Item);
                    }
                    else
                    {
                        removeDrafts.Add(i);
                    }
                }
                removeDrafts.ForEach(i => configuration.DraftItems.Remove(i));
            }

            try
            {
                Configuration.FullViewPage.ToInt64();
                ItemListerHelper.CurrentFullViewPage = CMSPage.Load(Configuration.FullViewPage.ToInt64());
            }
            catch { }

            string url = Request.Form["url"] != null ? Request.Form["url"] : Request.Url.ToString();
            if (!Configuration.MaintainList && Configuration.FullViewPage != null && Configuration.FullViewPage.Length > 0 && Configuration.FullViewPage.IsNumeric())
            {
                List<IItemListable> draftsToCheck = new List<IItemListable>();
                Configuration.DraftItems.ForEach(i => draftsToCheck.Add(i.Item));
                var urlItem = Activator.CreateInstance<T>().GetItemFromUrl(url, draftsToCheck);
                if (urlItem != null)
                {
                    targetItem = (T)urlItem;
                }
            }

            if (!string.IsNullOrEmpty(Configuration.SummaryView))
            {
                var viewPath = "~/Templates/" + typeof(T).FullName + "/" + Configuration.SummaryView + ".ascx";

                if (!Configuration.MaintainList && targetItem != null && !string.IsNullOrEmpty(Configuration.MainView))
                {
                    // Display individual item
                    viewPath = "~/Templates/" + typeof(T).FullName + "/" + Configuration.MainView + ".ascx";
                    //this.Controls.AddLiteral(GeneralUtils.RenderViewToString(
                    //        Oracle.CurrentControllerContext,
                    //        viewPath,
                    //        null,
                    //        new ViewDataDictionary(targetItem),
                    //        new TempDataDictionary()
                    //    ) + getItemJSONField(targetItem));
                    if (!string.IsNullOrEmpty(targetItem.MetaKeywords()))
                        CMSPage.Current.Keywords = targetItem.MetaKeywords();
                    if (!string.IsNullOrEmpty(targetItem.MetaDescription()))
                        CMSPage.Current.Description = targetItem.MetaDescription();
                    this.Controls.AddLiteral(GeneralUtils.RenderPartialToString(viewPath, targetItem) + getItemJSONField(targetItem));
                }
                else
                {
                    if (!File.Exists(Server.MapPath(viewPath)))
                    {
                        this.Controls.AddLiteral("<p>Template '" + Configuration.SummaryView + "' not found</p>");
                    }
                    else
                    {
                        // Display summary view
                        if (Core.User.Current.HasPermission(Permissions.Entity.Edit.SetValue(typeof(T).FullName)) ||
                            Core.User.Current.HasPermission(Permissions.Entity.Delete.SetValue(typeof(T).FullName)))
                        {
                            items.ForEach(i => this.Controls.AddLiteral(getItemJSONField(i)));
                        }

                        // Add paging template
                        if (Configuration.Pagination_Mode != PaginationMode.None && (Configuration.Pagination_Position == PaginationPosition.Both || Configuration.Pagination_Position == PaginationPosition.Top))
                        {
                            PaginationData pagingData = new PaginationData();
                            pagingData.PaginationMode = Configuration.Pagination_Mode;
                            pagingData.ItemsPerPage = Configuration.Items_Per_Page;
                            pagingData.CurrentPage = Configuration.Current_Page;
                            pagingData.TotalItemCount = this.ItemTotal;

                            string paginationView = "~/Templates/" + typeof(T).FullName + "/" + Configuration.PaginationView + ".ascx";

                            if (Configuration.Pagination_Mode == PaginationMode.Templated && File.Exists(Server.MapPath(paginationView)))
                            {
                                //this.Controls.AddLiteral(GeneralUtils.RenderViewToString(
                                //        Oracle.CurrentControllerContext,
                                //        paginationView,
                                //        null,
                                //        new ViewDataDictionary(pagingData),
                                //        new TempDataDictionary()
                                //    ));
                                this.Controls.AddLiteral(GeneralUtils.RenderPartialToString(paginationView, pagingData));
                            }
                            else
                            {
                                this.Controls.AddLiteral(buildPager(Configuration.Pagination_Mode, pagingData.TotalItemCount, pagingData.ItemsPerPage, Configuration.Current_Page));
                            }
                        }

                        if (Configuration.SummaryView.Contains("List/"))
                        {
                            //this.Controls.AddLiteral(GeneralUtils.RenderViewToString(
                            //    Oracle.CurrentControllerContext,
                            //    viewPath,
                            //    null,
                            //    new ViewDataDictionary(items),
                            //    new TempDataDictionary()
                            //));
                            this.Controls.AddLiteral(GeneralUtils.RenderPartialToString(viewPath, items));
                        }
                        else
                        {
                            foreach (var i in items)
                            {
                                //this.Controls.AddLiteral(GeneralUtils.RenderViewToString(
                                //    Oracle.CurrentControllerContext,
                                //    viewPath,
                                //    null,
                                //    new ViewDataDictionary(i),
                                //    new TempDataDictionary()
                                //));
                                this.Controls.AddLiteral(GeneralUtils.RenderPartialToString(viewPath, i));
                            }
                        }

                        // Add paging template
                        if (Configuration.Pagination_Mode != PaginationMode.None && Configuration.Pagination_Position == PaginationPosition.Both || Configuration.Pagination_Position == PaginationPosition.Bottom)
                        {
                            PaginationData pagingData = new PaginationData();
                            pagingData.PaginationMode = Configuration.Pagination_Mode;
                            pagingData.ItemsPerPage = Configuration.Items_Per_Page;
                            pagingData.CurrentPage = Configuration.Current_Page;
                            pagingData.TotalItemCount = this.ItemTotal;

                            string paginationView = "~/Templates/" + typeof(T).FullName + "/" + Configuration.PaginationView + ".ascx";

                            if (Configuration.Pagination_Mode == PaginationMode.Templated && File.Exists(Server.MapPath(paginationView)))
                            {
                                //this.Controls.AddLiteral(GeneralUtils.RenderViewToString(
                                //        Oracle.CurrentControllerContext,
                                //        paginationView,
                                //        null,
                                //        new ViewDataDictionary(pagingData),
                                //        new TempDataDictionary()
                                //    ));
                                this.Controls.AddLiteral(GeneralUtils.RenderPartialToString(paginationView, pagingData));
                            }
                            else
                            {
                                this.Controls.AddLiteral(buildPager(Configuration.Pagination_Mode, pagingData.TotalItemCount, pagingData.ItemsPerPage, Configuration.Current_Page));
                            }
                        }
                    }
                }
            }
        }

        public static string buildPager(PaginationMode mode, int TotalItemCount, int ItemsPerPage, int CurrentPage)
        {
            StringBuilder pager = new StringBuilder();

            if (ItemsPerPage < TotalItemCount)
            {

                pager.Append("<div class=\"cmsItemPager\">");
                if (ItemsPerPage != 0)
                {
                    if (mode == PaginationMode.Number_List)
                    {

                        pager.Append("    <ul>");
                        for (int i = 1; i <= (Math.Ceiling(Convert.ToDecimal(TotalItemCount) / Convert.ToDecimal(ItemsPerPage))); i++)
                        {
                            pager.AppendFormat("       <li{0}><a href=\"{1}\" class=\"goto\" rel=\"{2}\">{2}</a></li>", (i == CurrentPage ? " class=\"selected\"" : ""), GeneralUtils.RepageUrl(Oracle.Request.Url.AbsoluteUri.ToString(), i), i);
                        }
                        pager.Append("    </ul>");
                    }
                    else if (mode == PaginationMode.Previous_Next)
                    {
                        if (CurrentPage > 1)
                            pager.AppendFormat("    <a href=\"{0}\" class=\"prev\" ref=\"{1}\">Previous</a> | ", GeneralUtils.RepageUrl(Oracle.Request.Url.AbsoluteUri.ToString(), CurrentPage - 1), CurrentPage - 1);
                        else
                            pager.Append("    Previous | ");

                        if (CurrentPage < Math.Ceiling(Convert.ToDecimal(TotalItemCount) / Convert.ToDecimal(ItemsPerPage)))
                            pager.AppendFormat("<a href=\"{0}\" class=\"next\" ref=\"{1}\">Next</a>", GeneralUtils.RepageUrl(Oracle.Request.Url.AbsoluteUri.ToString(), CurrentPage + 1), CurrentPage + 1);
                        else
                            pager.Append("Next");

                    }
                }

                pager.Append("</div>");
            }

            return pager.ToString();
        }

        private string getItemJSONField(T item)
        {
            string elementID = item.GetType().FullName.Replace(".", "_") + "_" + item.GetIDAsString();
            return ContextItem.GetItem("cmsItemListAdded_" + elementID, () => false) ? "" : @"<textarea style=""display:none;"" id=""" + elementID + @""">" + item.ToJSON().Base64Encode() + "</textarea>";
        }
    }
}
